package com.config;

import com.domain.CustomUser;
import com.domain.Status;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.security.config.authentication.AuthenticationManagerFactoryBean.MISSING_BEAN_ERROR_MESSAGE;

@EnableWebSecurity(debug = true)
@Configuration
@AutoConfigureAfter(AuthenticationConfiguration.class)
public class ConfigExtendOpenId extends WebSecurityConfigurerAdapter {
    @Autowired
    GenericApplicationContext bf;

    // /wx?openId=11234
    @Slf4j
    public static class CustomForOpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        protected CustomForOpenIdAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationManager object) {
            super(defaultFilterProcessesUrl);
            setAuthenticationManager(object);
        }

        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

            String openid = request.getParameter("openId");
            if (!StringUtils.hasText(openid)) {
                throw new AuthenticationServiceException("no exists the openId param");
            }
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("openId", openid == null ? "" : openid);
            this.setDetails(request, authRequest);
            // ProviderManager
            return this.getAuthenticationManager().authenticate(authRequest);
        }

        protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
            authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
        }

    }

    @Slf4j
    public static class CustomAuthenticationProvider extends DaoAuthenticationProvider {

        public UserDetailsService userDetailsService() {
            ArrayList<CustomUser> users = new ArrayList<>();
            users.add(CustomUser.withUsername("user").password("{bcrypt}$2a$10$Nl8zvMdhS0l22hBR.hAsNuQeMWH0.dgVRC/4.a0ny37SiI2Hbc4iW").roles("USER").build());
            users.add(CustomUser.withUsername("user1").password("{bcrypt}$2a$10$6r4WkpfuAZ/k7UnoOwtXo.QsMTO.nBz2wZKtwG63t3VcFQj1BXxqC").roles("POST").build());
            CustomUser customUser = CustomUser.withUsername("openId").password("{bcrypt}$2a$10$Nl8zvMdhS0l22hBR.hAsNuQeMWH0.dgVRC/4.a0ny37SiI2Hbc4iW")
                    .nickname("虾片").status(Status.WECHAT).openId("123TYUWLKDHF1D89").roles("OPENID").build();
            users.add(customUser);
            return new CustomInMemoryUserDetailsManager(users);
        }

        public CustomAuthenticationProvider() {
            super();
            super.setUserDetailsService(userDetailsService());
        }

        @Override
        protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            Object principal = authentication.getPrincipal();
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            Set<String> collect = authorities.stream().map(grantedAuthority -> {
                String authority = grantedAuthority.getAuthority();
                return authority;
            }).collect(Collectors.toSet());

            // check the refresh time
            if (collect.contains("ROLE_OPENID") && principal.equals("openId")) {
                log.info("this is ROLE_OPENID user, try to get header image");
            }
            super.additionalAuthenticationChecks(userDetails, authentication);

        }
    }

    // 构建token
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable().authorizeRequests()
                .antMatchers("/api/**").hasRole("USER")
                .antMatchers("/openid/**").hasRole("OPENID")
                .antMatchers(HttpMethod.POST, "/post/**").hasRole("POST") // have interceptUrl
                .antMatchers("/anno/**").permitAll().antMatchers("/wx/**").permitAll()
                .anyRequest().authenticated()
                .and() // add anonymousfilter
                .formLogin().permitAll() // permit for login page.
                .usernameParameter("username").passwordParameter("password")
                .defaultSuccessUrl("/index.html")
                .failureUrl("/fail").and()
                .addFilterAfter(new CustomForOpenIdAuthenticationFilter("/wx/**",getObject()),
                        UsernamePasswordAuthenticationFilter.class).authenticationManager(getObject());
    }

    private static <T> T getBeanOrNull(ApplicationContext applicationContext, Class<T> type) {
        try {
            return applicationContext.getBean(type);
        } catch (NoSuchBeanDefinitionException notFound) {
            return null;
        }
    }

    private <T> T getBeanOrNull(Class<T> type) {
        try {
            return this.bf.getBean(type);
        } catch (NoSuchBeanDefinitionException noUds) {
            return null;
        }
    }
//   .authenticationProvider(new CustomAuthenticationProvider())
    // /wx?openid=1234
    public AuthenticationManager getObject() throws Exception {
        try {
            return (AuthenticationManager) this.bf.getBean(BeanIds.AUTHENTICATION_MANAGER);
        } catch (NoSuchBeanDefinitionException ex) {
            if (!BeanIds.AUTHENTICATION_MANAGER.equals(ex.getBeanName())) {
                throw ex;
            }
            CustomAuthenticationProvider provider = new CustomAuthenticationProvider();
            provider.afterPropertiesSet();
            return new ProviderManager(Arrays.<AuthenticationProvider>asList(provider));
        }
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/webjars/**");
    }
}
